#include <std.h>
#include <error.h>
#include <pipe.h> 
#include <type.h>
#include <memory.h>
#include <irq.h>
#include <sched.h>
#include <timer.h>
/**********************Kernel Function File*********************sched.h*/

static struct threadstate *head_thread;
static struct threadstate *sched_thread;	//Now the scheduler itself will run as a thread.. :)
static volatile unsigned long glob_id;

static unsigned nosleep=0;

#define DEFAULT_TMOUT 10

/*Following two functions are only used in asm in irq0 interrupt. These are not supposed
to be used anywhere else*/
extern void save_state_immediate();
extern void load_state_immediate();

/*Following two functions load thread states into temporary system states that will be loaded
at the end of irq0*/
int load_thread(CPU_state *);
int save_thread(CPU_state *);

static volatile unsigned long t;

static struct threadstate *running;

void init_thread(struct threadstate *state,void *args,unsigned long signal)
{
	if(!state)
		stop("\nInvalid init_thread");
	init_cpu_state(&state->cpustate);
	set_stack(state,state->entry_fn,state->stack,args,signal);
	t=state->tmout;
	state->sleep_state=AWAKE;
	state->idle_timer=NULL;
	state->pipe_head=NULL;
	state->cpu_time=NULL;
}

/*To protect below function from corruption we can't use spinlocks. If the function is interrupted before
the completion of the update of the thread list then the scheduler might end up in
running an incomplete thread entry which might lead to faults*/

u32 create_thread(char *name,void *entry,void *args,unsigned long signal)
{
	struct threadstate *new;
	sane(!entry,"create_thread:Entry is null");
	kernel_lock();
	add_node_end(struct threadstate,head_thread,new);
	new->tmout=(running->tmout)/2;
	running->tmout=new->tmout;
	new->entry_fn=entry;
	new->stack=allocate_thread_stack();
	new->thread_id=++glob_id;
	strcpy(new->thread_name,name);
	init_thread(new,args,signal);
	kernel_unlock();
	return new->thread_id;
}

struct threadstate *state_create_thread(char *name,void *entry,void *args,unsigned long signal)
{
	struct threadstate *new;
	sane(!entry,"create_thread:Entry is null");
	kernel_lock();
	add_node_end(struct threadstate,head_thread,new);
	new->tmout=DEFAULT_TMOUT;
	new->entry_fn=entry;
	new->stack=allocate_thread_stack();
	new->thread_id=++glob_id;
	strcpy(new->thread_name,name);
	init_thread(new,args,signal);
	kernel_unlock();
	return new;
}

#define increase_timeslice(tstate0)	({ \
	tstate0->tmout++; \
})

#define decrease_timeslice(tstate0)	({ \
	if((tstate0->tmout)>0) \
		tstate0->tmout--; \
})

/* This function does not modify the thread list. Hence a spinlock.*/
unsigned long get_thread_id(char *name)
{
	struct threadstate *temp;
	for(temp=head_thread;(strcmp(temp->thread_name,name)!=NULL) && temp!=NULL;temp=temp->next);
	if(temp==NULL)	{
		printk("\nThread %s not found",name);
		return NULL;
	}
	return temp->thread_id;
}

int free_thread_stack(u32);
int delete_thread(unsigned long id)
{
	static struct threadstate *temp;
	if(id==0)	{
		printk("\nInvalid id.");
		return NULL;
	}
	//get_node(struct threadstate,head_thread,thread_id,id,temp);
	temp=head_thread;
	for(temp=head_thread;temp->thread_id!=id;temp=temp->next)	{
		if(temp==NULL)	{
			printk("\nthread not found.");
			return NULL;
		}
	}
	kernel_lock();
	free_thread_stack(temp->stack);
	running->tmout+=temp->tmout;
	delete_node(struct threadstate,head_thread,temp);	
	kernel_unlock();
	/*Now if a thread ended itself then the printk would not even get time to show the message hence only
	after the printk the preemption is started*/
	return NULL;
}	

unsigned long running_thread_id();

int end_all_threades()
{
	struct threadstate *temp;
	kernel_lock();
	printk("\nEnding all threads");
	for(temp=head_thread->next;temp!=NULL;temp=temp->next)	{
		delete_node(struct threadstate,head_thread,temp);
	}
	kernel_unlock();
	return NULL;
}

void show_all_threades()
{
	struct threadstate *temp;
	printk("\nThreads:");
	for(temp=head_thread;temp!=NULL;temp=temp->next)	{
		printk("\nThread:%s",temp->thread_name);
	}
}


static volatile int fsched=0;
static struct threadstate *switch_to;

void switchnext()
{
	fsched=0;
	if(running!=NULL)	{
		save_thread(&running->cpustate);
		running->cpu_time+=running->tmout-t;
		running=running->next;
		while(running && running->sleep_state!=AWAKE)	{
			if((running->sleep_state)==TEMP_SLEEP)
				running->sleep_state=AWAKE;
			if((running->sleep_state)==DET_SLEEP)	{
				if(--(running->sleep_count)<=0)
					running->sleep_state=AWAKE;
			}				
			running=running->next;
		}
		if(running==NULL)	{
			running=head_thread;
		}
	}
	else	{
		running=head_thread;
	}
	if(running==NULL)	{
		stop("\nNo thread");
	}
	if(switch_to)	{
		switch_to=NULL;
		load_thread(&switch_to->cpustate);
	} else
		load_thread(&running->cpustate);
	t=running->tmout;
}

/*The great scheduler thread*/
void _scheduler(void *args)
{
	while(1)	{
		mdelay(100);
		printk(".");
	}
}

void wait_sched()
{
	fsched=1;
	while(fsched);
}

unsigned long get_my_pid()
{
	return running->thread_id;
}

void sleep_thread(unsigned long id)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	tmp->sleep_state=SLEEP;
	tmp->sleep_count=0;
}

void wake_thread(unsigned long id)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	tmp->sleep_state=AWAKE;
	tmp->sleep_count=0;
}

void temp_sleep_thread(unsigned long id)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	tmp->sleep_state=TEMP_SLEEP;
	tmp->sleep_count=0;
}

/*Predetermined sleep period*/
void det_sleep_thread(unsigned long id,int count)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	tmp->sleep_state=DET_SLEEP;
	tmp->sleep_count=count;
}

void set_time(unsigned long id,unsigned long time)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	tmp->tmout=time;
}

void dec_time(unsigned long id)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	if(tmp->tmout>1)
		tmp->tmout--;
}

void inc_time(unsigned long id)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	tmp->tmout++;
}

void default_time(unsigned long id)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	tmp->tmout=10;
}

void least_time(unsigned long id)
{
	struct threadstate *tmp;
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	tmp->tmout=1;
}

void idle_me(void (*fn)(void),unsigned long millisecs)
{
	struct timer_call *idlet;
	struct threadstate *tmp;
	kernel_lock();
	tmp=running;
	idlet=new(struct timer_call);
	idlet->entry=fn;
	idlet->flags=TIMER_FUNCTION;
	idlet->ms=millisecs;
	add_timer(idlet);
	tmp->sleep_state=SLEEP;
	tmp->sleep_count=0;
	tmp->idle_timer=idlet;
	kernel_unlock();
}
	
void wake_idle(unsigned long id)
{
	struct threadstate *tmp;
	kernel_lock();
	get_node(struct threadstate,head_thread,thread_id,id,tmp);
	if(tmp->idle_timer!=NULL)
		remove_timer(tmp->idle_timer);
	tmp->sleep_state=AWAKE;
	tmp->sleep_count=0;
	tmp->idle_timer=NULL;
	kernel_unlock();
}

/*This fn makes the current thread sleep.
It can only be woken up by any other thread */
void sleep()
{
	kernel_lock();
	running->sleep_state=SLEEP;
	running->sleep_count=0;
	kernel_unlock();
	wait_sched();
}

void temp_sleep()
{
	running->sleep_state=TEMP_SLEEP;
	running->sleep_count=0;
}

void det_sleep(int count)
{
	running->sleep_state=DET_SLEEP;
	running->sleep_count=count;
}

unsigned long running_thread_id()
{
	return running->thread_id;
}

CPU_state *currentthread()
{
	return (&running->cpustate);
}

char *running_thread_name()
{
	return running->thread_name;
}

int scheduler_up()
{
	return head_thread!=NULL ? 1:NULL;
}

static volatile u32 sched_time;

void scheduler()
{
	sched_time++;
	if(t>0)	{ 
		t--;
		if(!t)
			switchnext();
	}
}

void make_sched()
{
	t=1;
}

void get_sched_time(u32 *t)
{
	*t=sched_time; 
}

int add_pipe_thread(struct pipe *p)
{
	struct threadstate *temp;
	struct pipe_list *ptemp;
	temp=running;
	if(temp->pipe_head==NULL)	{
		create_head(struct pipe_list,temp->pipe_head);
		temp->pipe_head->pipe_handle=p;
	}
	add_node_end(struct pipe_list,temp->pipe_head,ptemp);
	ptemp->pipe_handle=p;
	return SUCCESS;
}

int remove_pipe_thread(struct pipe *p)
{
	struct threadstate *temp;
	struct pipe_list *ptemp,*ptemp2;
	temp=running;
	if(temp->pipe_head->pipe_handle==p)	{
		ptemp2=temp->pipe_head;
		ptemp=temp->pipe_head->next;
		temp->pipe_head=ptemp;
		free(ptemp2);
		return SUCCESS;
	}
	get_node(struct pipe_list,temp->pipe_head,pipe_handle,p,ptemp);
	delete_node(struct pipe_list,temp->pipe_head,ptemp);	
	return SUCCESS;
}

void post_sched_init();

u32 allocate_thread_stack()
{
	char *stack;
	stack=(char *)get_mem(DEFAULT_STACK_SIZE+STACK_SAFE);
	stack+=DEFAULT_STACK_SIZE; /*4kB stack*/
	return (u32)stack;
}

/*Warning:This function can only free the stack allocated by the function allocate_thread_stack()*/
int free_thread_stack(u32 stack)
{
	stack-=DEFAULT_STACK_SIZE;
	return free((void *)stack);
}

void exit(int errno)
{
	printk("\nError:%d",errno);
	delete_thread(running_thread_id());
	while(1);
}

void main_thread()
{
	post_sched_init();
	while(1);
}

void _idle()
{
	unsigned long mainid;
	head_thread->tmout=80;
	mainid=create_thread("main",main_thread,NULL,NULL);
	head_thread->tmout=1;
	while(1)	{
		asm("hlt");
	}
}


void list_threads()
{
	printk("\nListing threads:");
	struct threadstate *temp;
	for(temp=head_thread;temp!=NULL;temp=temp->next)	{
		printk("\n%s",temp->thread_name);
		printk("\n stack:%x timeslice:%d cpu_time:%dsec",temp->stack,temp->tmout,temp->cpu_time/SYSTEM_SCHEDULE_CLOCK_SPEED);
		show_state(&temp->cpustate);
	}
}

/*Asynchronous signalling functions*/

/*Send a signal to a thread by reentering its function... :) */
void thread_send_signal(unsigned long pid,unsigned long signal)
{
	struct threadstate *sendto;
	get_node(struct threadstate,head_thread,thread_id,pid,sendto);
	if(!sendto)	{
		printk("\nThread %d not found",pid);
		return;
	}
	create_thread("signal",sendto->entry_fn,NULL,signal);
}

/*This is the same function except that it can send a signal to any damn function provided it receives. If
the function has no code to receive the signal then the function will simply run again creating a mess.*/
/*In this case the function is declared as foo(void *args,unsigned long signal) as it is now a thread.*/
void function_send_signal(void (*fn)(void *args,unsigned long signal),unsigned long signal)
{
	create_thread("signal",fn,NULL,signal);
}


void init_sched()
{
	printk("\nScheduler initiating");
	kernel_lock(); 
	running=NULL;
	glob_id=0;
	/*setup the main thread*/
	create_head(struct threadstate,head_thread);
	head_thread->tmout=10;
	head_thread->stack=allocate_thread_stack();
	head_thread->entry_fn=_idle;
	head_thread->thread_id=++glob_id;
	strcpy(head_thread->thread_name,"idle");
	init_thread(head_thread,NULL,NULL);
	wake_thread(head_thread->thread_id);
	request_irq(NULL,0,scheduler);
	enable_irq(0);
	create_setting_val("nosleep",&nosleep);
	kernel_unlock();
	mdelay(5000);
	stop("Schedule failed!");
}
